#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include "compiler.h"
#include "hcb_comn.h"
#include "sf.h"




uint32_t  sf_hw_get_id( void )
{
	uint32_t tmp;

	/*read id*/
	write_reg( BASE_AP_FLASH + SF_FLASH_ID_REQ, (0x9f<<8) | (0x20<<16) | 0x1);
	
    __sync_synchronize();
	//wait until return flash id
	while( read_reg( BASE_AP_FLASH+SF_FLASH_ID_REQ) & 0x1);

	tmp = read_reg( BASE_AP_FLASH + SF_FLASH_ID_VAL);
	return tmp;
}


/*
 * register order is big endian
 * but fifo order is little endian
 */
void  sf_reg_read(int index, uint32_t *value)
{
	*value = read_reg( BASE_AP_FLASH + SF_REGS_ADDR + (index<<2) );
}

void  sf_reg_write(int index, uint32_t value)
{
	write_reg( BASE_AP_FLASH + SF_REGS_ADDR + (index<<2), value );
}


/*FIFO*/
#define SF_FLASH_FIFO_ADDR 0x2000000
#define SF_FIFO_ENTRYS 256


static inline int sf_fifo_read( uint8_t *buf, int rsize )
{
	uint32_t temp;
	int cur,level,entry;

	/* word, align check */
	if ( (uint32_t)buf & 0x3 )
	{
		return -2;
	}
	entry = rsize>>2;

	level = read_reg( BASE_AP_FLASH + SF_FLASH_FLR) & 0xffff;
	entry = entry<level?entry:level;

	if ( entry == 0 )
	{
		return 0;
	}

	cur = 0;
	while ( cur < entry )
	{
		temp = read_reg( SF_FLASH_FIFO_ADDR );
		*((uint32_t *)buf+cur) = temp;

		cur = cur + 1;
	}

	return entry<<2;
}


/*return data size that has already been written to fifo*/
static inline int sf_fifo_write(uint8_t *buf, int wsize )
{
	uint32_t temp;
	int cur,level,entry;
	if ( buf == NULL )
	{
		return -1;
	}

	if ( (uint32_t)buf & 0x3 )
	{
		return -2;
	}
	entry = wsize>>2;

	level = (read_reg( BASE_AP_FLASH +SF_FLASH_FLR) >> SF_FLASH_FLR_WRITE_SHIFT) & 0xffff;
	entry = entry<(SF_FIFO_ENTRYS - level)?entry:(SF_FIFO_ENTRYS - level);
	
	if ( entry == 0 )
	{
		return 0;
	}

	cur = 0;
	while ( cur < entry )
	{
		temp = *((uint32_t *)buf+cur);
		write_reg(SF_FLASH_FIFO_ADDR, temp);

		cur = cur + 1;
	}

	//sf_ctl_io("hold");
	return entry<<2;
}

void sf_set_div(uint8_t div)
{
	uint32_t temp = read_reg(BASE_AP_FLASH+SF_FLASH_SCK);
	temp = temp & ~0xff;
	temp = temp | (uint32_t)div;
	write_reg(BASE_AP_FLASH+SF_FLASH_SCK, temp);
}

void sf_get_div(uint8_t *div)
{
	uint32_t temp = read_reg(BASE_AP_FLASH + SF_FLASH_SCK);
	*div = (uint8_t)temp;
}

void sf_fifo_reset()
{
	//reset
	write_reg(BASE_AP_FLASH+SF_FLASH_RXFIFO_RESET, 1);
	while(read_reg(BASE_AP_FLASH+SF_FLASH_RXFIFO_RESET) == 1);
	write_reg(BASE_AP_FLASH+SF_FLASH_TXFIFO_RESET, 1);
	while(read_reg(BASE_AP_FLASH+SF_FLASH_TXFIFO_RESET) == 1);
}


int  sf_sync_start_read( uint32_t stt, uint8_t * rx_buf, int rx_size )
{
	int rx_delays;
	int n;

	/**/
	write_reg(BASE_AP_FLASH+SF_FLASH_USER_CMD ,0);
	plat_delay(2);

	/* word mode */
	write_reg(BASE_AP_FLASH+SF_FLASH_FIFO_WIDTH, SF_FLASH_FIFO_WORD);

	/* reset FIFO */
	sf_fifo_reset();

	// start program
	write_reg(BASE_AP_FLASH+SF_FLASH_USER_CMD, (stt << 8) | 1 );

	rx_delays = 0;

	while ( rx_size > 0 )
	{
		n = sf_fifo_read( rx_buf, rx_size );
		if ( n < 0 )
		{
			/* fail.. */
			return n;
		}
		else if ( n == 0 )
		{
			rx_delays++;
		}
		else
		{
			rx_size = rx_size - n;
			rx_buf = rx_buf + n;
			rx_delays = 0;
		}
	}

	if ( rx_delays > 1000000 )
	{
		printf("ERROR: wait for data timeout, remaining %d \n", rx_size );
		return -2;
	}

	/*wait until sf program is done*/
#if 1
	int timeout = 1000;

	while ( timeout-- )
	{
		if((read_reg(BASE_AP_FLASH+SF_FLASH_USER_CMD) & 0x1) == 0)
		{
			/*if timeout event occur,else:break*/
			break;
		}
		plat_delay(10);
	}

	if ( timeout <= 0 )
	{
		printf( "wait finish, timeout\n" );
		return -3;
	}

#endif

	return 0;
}



int  sf_sync_start_write( uint32_t stt, uint8_t * tx_buf, int tx_size )
{
	int cur;
	int tx_delays;
	int n;
	int enable;

	/* word mode */
	write_reg( BASE_AP_FLASH + SF_FLASH_FIFO_WIDTH, SF_FLASH_FIFO_WORD );
	
	/* reset FIFO */
	sf_fifo_reset();

	enable = 0;
	tx_delays = 0;

	while ( tx_size > 0 )
	{
		n = sf_fifo_write( tx_buf, tx_size );
		if ( n < 0 )
		{
			return n;
		}
		else if ( n == 0 )
		{
			tx_delays++;
		}
		else
		{
			tx_size = tx_size - n;
			tx_buf = tx_buf + n;
			tx_delays = 0;
		}

		if ( enable == 0 )
		{
			//enable
			write_reg( BASE_AP_FLASH+SF_FLASH_USER_CMD, (stt << 8) | 1 );
			enable = 1;
		}

		if ( tx_delays > 1000000 )
		{
			printf("ERROR: wait for data timeout, remaining %d\n", tx_size );
			return -2;
		}
	}

	/*wait until sf is done*/
#if 1
	int timeout = 10000;

	while ( timeout-- )
	{
		if((read_reg(BASE_AP_FLASH+SF_FLASH_USER_CMD) & 0x1) == 0)
		{
			/*if timeout event occur,else:break*/
			break;
		}
		plat_delay(10);
	}
#endif

	return 0;
}


int  sf_sync_start_nofifo( uint32_t stt )
{
	int cur;
	const uint32_t *addr_p;

	// start program
	write_reg( BASE_AP_FLASH+SF_FLASH_USER_CMD, (stt << 8) | 1);

	/*wait until sf program is done*/
#if 1
	int timeout = 1000000;

	while ( timeout-- )
	{
		if((read_reg(BASE_AP_FLASH+SF_FLASH_USER_CMD) & 0x1) == 0)
		{
			/*if timeout event occur,else:break*/
			break;
		}
		plat_delay(10);
	}

	if ( timeout <= 0 )
	{
		printf( "wait finish, timeout\n" );
		return -3;
	}
#endif

	return 0;
}





void  sf_reset( void )
{
	//write_reg(SF_AP_BASE+SF_FLASH_STATE_RESET, 1);
	write_reg(BASE_AP_SYSCTL+0x7c, 0);
	plat_delay(10);
	write_reg(BASE_AP_SYSCTL+0x7c, 1);
	plat_delay(10);

	/**/
	sf_fifo_reset();
}


